/**
 * Copyright (C) 2012 Oleg Anastassov <o.anastasov@gmail.com>
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package kz.kstu.ent.db.gererator;


import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author John Thompson
 */
public final class DDLGenerator {

    private static final Dialect DEFAULT_DIALECT = Dialect.H2;

    private static final String ENTITY_PACKAGE_NAME = "kz.kstu.ent.business.entity";
    private static final String SCRIPT_DIRECTORY = "ent-db\\src\\main\\resources\\generated";
    private static final String DDL_PREFIX = "ddl_ent_";
    private static final String DDL_PROPERTY = "hibernate.hbm2ddl.auto";
    private static final String CREATE_PROPERTY_VALUE = "create";
    private static final String DIALECT_PROPERTY = "hibernate.dialect";
    private static final String SQL_EXTENSION = ".sql";
    private static final String CLASS_EXTENSION = ".class";
    private static final String SVN = "svn";
    private static final String DELIMITER = ";";

    private Configuration configuration;

    private DDLGenerator() {
    }

    public DDLGenerator(String packageName) throws Exception {
        configuration = new Configuration();
        configuration.setProperty(DDL_PROPERTY, CREATE_PROPERTY_VALUE);

        for (Class clazz : getClasses(packageName)) {
            configuration.addAnnotatedClass(clazz);
        }
    }

    /**
     * Method that actually creates the file.
     *
     * @param dialect to use
     */
    private void generate(Dialect dialect, String fileName) {
        configuration.setProperty(DIALECT_PROPERTY, dialect.getDialectClass());
        SchemaExport export = new SchemaExport(configuration);
        export.setDelimiter(DELIMITER);
        export.setOutputFile(fileName);
        export.execute(true, false, false, false);
    }

    /**
     * @param args some arguments
     * @throws Exception throws if error has occurred
     */
    public static void main(String[] args) throws Exception {
        Dialect dialect;
        String fileName;
        if (args.length == 2) {
            String dialectName = args[0].toUpperCase();
            dialect = Dialect.valueOf(dialectName);
            fileName = args[1];
        } else {
            dialect = DEFAULT_DIALECT;
            fileName = DDL_PREFIX + dialect.name().toLowerCase() + SQL_EXTENSION;
        }
        DDLGenerator gen = new DDLGenerator(ENTITY_PACKAGE_NAME);
        gen.generate(dialect, getScriptDirectory() + '/' + fileName);
    }

    /**
     * Utility method used to fetch Class list based on a package name.
     *
     * @param packageName (should be the package containing your annotated beans.
     * @return list of classes in specified package
     * @throws Exception throws if error has occurred
     */
    private List<Class> getClasses(String packageName) throws Exception {
        List<Class> classes = new ArrayList<Class>();
        File directory = null;
        try {
            ClassLoader cld = Thread.currentThread().getContextClassLoader();
            if (cld == null) {
                throw new ClassNotFoundException("Can't get class loader.");
            }
            String path = packageName.replace('.', '/');
            URL resource = cld.getResource(path);
            if (resource == null) {
                throw new ClassNotFoundException("No resource for " + path);
            }
            directory = new File(resource.getFile());
            if (!directory.exists()) {
                throw new ClassNotFoundException(packageName + " is not a valid package");
            }
            for (String fileName : directory.list()) {
                if (fileName.endsWith(CLASS_EXTENSION)) {
                    // removes the .class extension
                    classes.add(Class.forName(packageName + '.'
                            + fileName.substring(0, fileName.length() - CLASS_EXTENSION.length())));
                } else {
                    File childFile = new File(resource.getFile() + '/' + fileName);
                    if (childFile.isDirectory() && !childFile.getName().endsWith(SVN)) {
                        List<Class> childClasses = getClasses(packageName + '.' + fileName);
                        classes.addAll(childClasses);
                    }
                }
            }
        } catch (NullPointerException x) {
            throw new ClassNotFoundException(packageName + " (" + directory + ") does not appear to be a valid package");
        }
        return classes;
    }

    private static String getScriptDirectory() {
        File directory = new File(SCRIPT_DIRECTORY);
        if (!directory.exists() || !directory.isDirectory()) {
            directory.mkdir();
        }
        return directory.getPath();
    }

    /**
     * Holds the class names of hibernate dialects for easy reference.
     */
    private static enum Dialect {
        ORACLE("org.hibernate.dialect.Oracle10gDialect"),
        MYSQL("org.hibernate.dialect.MySQLDialect"),
        H2("org.hibernate.dialect.H2Dialect"),
        HSQL("org.hibernate.dialect.HSQLDialect");

        private String dialectClass;

        private Dialect(String dialectClass) {
            this.dialectClass = dialectClass;
        }

        public String getDialectClass() {
            return dialectClass;
        }
    }
}

